package com.guosen.zebra.framework.starter.config.service.impl;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.guosen.zebra.framework.starter.config.constants.ZebraConfigScope;
import com.guosen.zebra.framework.starter.config.constants.ZebraConfigType;
import com.guosen.zebra.framework.starter.config.constants.ZebraConstants;
import com.guosen.zebra.framework.starter.config.exception.ZebraConfigException;
import com.guosen.zebra.framework.starter.config.internal.ZebraConfigHttpClient;
import com.guosen.zebra.framework.starter.config.properties.ZebraConfigProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.util.StringUtils;

import java.io.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;

@Order(0)
public class ZebraPropertySourceLocator implements PropertySourceLocator {
    private static final Logger LOGGER = LoggerFactory.getLogger(ZebraPropertySourceLocator.class);

    private String idc;
    private String set;
    private String node;

    private final ZebraConfigProperties zebraConfigProperties;

    public ZebraPropertySourceLocator(ZebraConfigProperties zebraConfigProperties) {
        this.zebraConfigProperties = zebraConfigProperties;
    }


    @Override
    public PropertySource<?> locate(Environment environment) {
        String confName = zebraConfigProperties.getName();
        String serverAddr = zebraConfigProperties.getServerAddr();
        if (!StringUtils.hasLength(confName))
            throw new IllegalArgumentException("There is not conf name.");
        fromEnv();
        CompositePropertySource composite = new CompositePropertySource("zebra");
        try {
            loadConf(composite, confName, ZebraConfigType.RES_CONF, serverAddr);
            loadConf(composite, confName, ZebraConfigType.BUSS_CONF, serverAddr);
            if (composite.getPropertySources().isEmpty())
                throw new ZebraConfigException("Get nothing from zebra config center.");
            generateLocalCacheProperties(composite);
        } catch (ZebraConfigException | IOException e) {
            LOGGER.error("Failed to get conf from zebra-conf error message: {}" , e.getMessage(), e);
            loadLocalCacheProperties(composite);
        }
        return composite;
    }

    private void fromEnv() {
        // 支持容器平台需求，容器平台里面微服务的start.ini不再动态生成
        // 当前用到的IDC值从环境变量中传递进来
        try {
            Map<String, String> envMap = System.getenv();
            if (envMap == null || envMap.isEmpty()) {
                return;
            }

            for (Map.Entry<String, String> entry : envMap.entrySet()) {
                String envKey = entry.getKey();
                String envValue = entry.getValue();
                handleEnv(envKey, envValue);
            }
        }
        catch (Exception e) {
            LOGGER.error("Failed to get from environment, error message : {}", e.getMessage(), e);
        }
    }

    private void handleEnv(String envKey, String envValue) {
        String lowCaseEnvKey = envKey.toLowerCase();
        switch (lowCaseEnvKey) {
            case ZebraConfigScope.SCOPE_IDC:
                idc = envValue;
                break;
            case ZebraConfigScope.SCOPE_SET:
                set = envValue;
                break;
            case ZebraConfigScope.SCOPE_NODE:
                node = envValue;
                break;
            default:
                break;
        }
    }

    private void loadConf(CompositePropertySource composite, String confName, String type, String addr) {
        ArrayNode zebraConfigAry = getZebraConfig(confName, type, addr);
        if (zebraConfigAry == null || zebraConfigAry.isEmpty()) {
            return;
        }

        for (int i = 0; i < zebraConfigAry.size(); i++) {
            JsonNode configItem = zebraConfigAry.get(i);
            String scope = configItem.get("scope").asText();
            String scopeName = configItem.get("scopeName").asText();
            String configText = configItem.get("text").asText();
            if (!isScopeMatch(scope, scopeName) || !StringUtils.hasLength(configText)) {
                continue;
            }
            try {
                Properties properties = new Properties();
                properties.load(new StringReader(configText));
                PropertiesPropertySource propertiesPropertySource = new PropertiesPropertySource(type + "conf" + i, properties);
                composite.addPropertySource(propertiesPropertySource);
            } catch (IOException e) {
                LOGGER.error(e.getMessage(), e);
            }
        }
    }

    private boolean isScopeMatch(String scope, String scopeName) {
        if (ZebraConfigScope.SCOPE_GLOBAL.equals(scope)) {
            return true;
        }
        if (ZebraConfigScope.SCOPE_IDC.equals(scope) && Objects.equals(idc, scopeName)) {
            return true;
        }
        if (ZebraConfigScope.SCOPE_SET.equals(scope) && Objects.equals(set, scopeName)) {
            return true;
        }
        return ZebraConfigScope.SCOPE_NODE.equals(scope) && Objects.equals(node, scopeName);
    }

    private ArrayNode getZebraConfig(String confName, String type, String addr) {
        try {
            Map<String, String> parameters = buildGetZebraConfigParameters(confName, type);
            String url = addr + ZebraConstants.GET_ZEBRA_CONFIG_PATH;
            JsonNode result = ZebraConfigHttpClient.get(url, new HashMap<>(), parameters);
            if (result.get("code").asInt() == 1)
                throw new ZebraConfigException(String.format("Failed to request url: %s, result: %s.", url, result));
            return (ArrayNode) result.get("data");
        } catch (Exception e) {
            LOGGER.error("Failed to get config : {}, type : {}, error message : {}", confName, type, e.getMessage(), e);
            throw new ZebraConfigException("Failed to get zebra config.");
        }
    }

    /**
     * 生成本地配置文件
     */
    private void generateLocalCacheProperties(CompositePropertySource composite) throws IOException {
        Properties p = new Properties();
        for (PropertySource<?> propertySource : composite.getPropertySources()) {
            Properties source = (Properties) propertySource.getSource();
            p.putAll(source);
        }
        try (FileOutputStream out = new FileOutputStream(getLocalCachePropertiesPath())) {
            // 为properties添加注释
            p.store(out, "zebra auto generate properties");
        }
    }


    /**
     * 加载本地配置文件
     */
    private void loadLocalCacheProperties(CompositePropertySource composite) {
        LOGGER.info("********** Try to load localCache.properties. **********");
        try (FileInputStream in = new FileInputStream(getLocalCachePropertiesPath())) {
            Properties properties = new Properties();
            properties.load(in);
            PropertiesPropertySource propertiesPropertySource = new PropertiesPropertySource("localCacheZebraConf", properties);
            composite.addPropertySource(propertiesPropertySource);
            LOGGER.info("********** Load localCache.properties successfully. **********");
        } catch (Exception e) {
            throw new ZebraConfigException("Failed to load localCache.properties", e);
        }
    }

    /**
     * 获取linux本地部署生成缓存配置文件路径
     */
    private static String getLocalCachePropertiesPath() {
        return getLocalPubPath() + "/" + ZebraConstants.ZEBRA_PROPERTIES_NAME;
    }

    /**
     * 获取linux本地部署路径
     */
    private static String getLocalPubPath() {
        String jarWholePath = ZebraPropertySourceLocator.class.getProtectionDomain().getCodeSource().getLocation().getFile();
        try {
            jarWholePath = java.net.URLDecoder.decode(jarWholePath, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            LOGGER.error(e.getMessage(), e);
        }
        String jarPath = new File(jarWholePath).getParentFile().getAbsolutePath();
        return jarPath.replaceAll("/lib", "");
    }

    private Map<String, String> buildGetZebraConfigParameters(String confName, String type) {
        Map<String, String> parameters = new HashMap<>();
        parameters.put("server", confName);
        parameters.put("type", type);
        return parameters;
    }
}
